#include "Game.h"
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#define MY_VK_DIRE         0x00E0
#define MY_VK_DIRE_UP      0x0048
#define MY_VK_DIRE_DOWN    0x0050
#define MY_VK_DIRE_LEFT    0x004B
#define MY_VK_DIRE_RIGHT   0x004D
bool Game::end_of_game()
{
    return this->ended;
}
Game::Game(PEN pen)
{
    this->ended = false;
    this->originalY = 0;
    this->curPos = { 0,0 };
    this->pen = pen;
    srand(time(nullptr));
    for (int i = 0; i < 4; i++)
    {
        int k = rand() % 64;
        while (this->placed.at(k))
        {
            k = rand() % 64;
        }
        this->placed.set(k);
    }
}

Game::Game(PEN pen,unsigned long seed)
{
    this->ended = false;
    this->originalY = 0;
    this->curPos = { 0,0 };
    this->pen = pen;
    srand(seed);
    for (int i = 0; i < 4; i++)
    {
        int k = rand() % 64;
        while (this->placed.at(k))
        {
            k = rand() % 64;
        }
        this->placed.set(k);
    }
}

static bool isLegalPosition(COORD pos)
{
    return !(pos.X < 1 || pos.X>8 || pos.Y < 1 || pos.Y>8);
}
static COORD operator+(COORD a, COORD b)
{
    return { SHORT(a.X + b.X),SHORT(a.Y + b.Y) };
}
static bool operator==(COORD a, COORD b)
{
    return (a.X == b.X) && (a.Y == b.Y);
}
COORD RotateCW(COORD pos)
{
    return { pos.Y,SHORT(-pos.X) };
}
COORD RotateACW(COORD pos)
{
    return { SHORT(-pos.Y),pos.X };
}
COORD Game::go(COORD pos, COORD dire)
{
    if ((dire.X * dire.X) + (dire.Y * dire.Y) != 1)
    {
        this->moveCur({ 0,10 });
        this->pen.Write(0x04,"illegal direction");
        exit(-1);
        return pos;
    }
    if (!isLegalPosition(pos + dire))
    {
        this->moveCur({ 0,10 });
        this->pen.Write(0x04, "illegal position or direction");
        exit(-1);
        return pos;
    }
    if (this->placed.at((pos + dire).X - 1, (pos + dire).Y - 1))
    {
        return pos + dire;
    }
    if (isLegalPosition(pos + dire + RotateCW(dire)))
        if (this->placed.at((pos + dire + RotateCW(dire)).X - 1, (pos + dire + RotateCW(dire)).Y - 1))
        {
            return pos;
        }
    if (isLegalPosition(pos + dire + RotateACW(dire)))
        if (this->placed.at((pos + dire + RotateACW(dire)).X - 1, (pos + dire + RotateACW(dire)).Y - 1))
        {
            return pos;
        }
    pos = pos + dire;
    while (1)
    {
        if (!isLegalPosition(pos))
            return pos;
        if (!isLegalPosition(pos + dire))
            return pos + dire;
        if (this->placed.at((pos + dire).X - 1, (pos + dire).Y - 1))
            return pos + dire;
        if (isLegalPosition(pos + dire + RotateCW(dire)) && (this->placed.at((pos + dire + RotateCW(dire)).X - 1, (pos + dire + RotateCW(dire)).Y - 1)))
        {
            dire = RotateACW(dire);
            continue;
        }
        if (isLegalPosition(pos + dire + RotateACW(dire)) && (this->placed.at((pos + dire + RotateACW(dire)).X - 1, (pos + dire + RotateACW(dire)).Y - 1)))
        {
            dire = RotateCW(dire);
            continue;
        }
        pos = pos + dire;
    }
    return COORD();
}

void Game::StartGame()
{
    COORD CurPos; 
    if (!this->pen.GetCursorPos(CurPos))
    {
        cout << "Fail to get cursor position" << endl;
        exit(-1);
    }
    if (CurPos.X != 0)
    {
        this->pen.NewLine();
        CurPos.Y++;
    }
    this->originalY = CurPos.Y;
    this->pen.SetColor(0x07);
    this->pen.Write("  ");
    for (int i = 0; i < 8; i++)this->pen.Write("  ");
    this->pen.Write("  ");
    for (int i = 0; i < 8; i++)
    {
        this->pen.NewLine();
        this->pen.Write("  ");
        for (int i = 0; i < 8; i++)this->pen.Write("_ ");
        this->pen.Write("  ");
    }
    this->pen.NewLine();
    this->pen.Write("  ");
    for (int i = 0; i < 8; i++)this->pen.Write("  ");
    this->pen.Write("  ");
    this->curPos = { 0,0 };
    this->moveCur({ 0,0 });
}
void Game::onkeydown(int ch)
{
    if (ch == VK_ESCAPE)
    {
        this->moveCurTo({ 0,11 });
        this->ended = true;
        return;
    }
    switch (ch)
    {
    case VK_SPACE:
        this->onclick();
        break;
    case VK_RETURN:
        this->oncheck();
        break;
    case MY_VK_DIRE:
        ch = _getch();
        switch (ch)
        {
        case MY_VK_DIRE_UP:
            if (this->curPos.Y != 0)
                this->moveCur({ 0,-1 });
            break;
        case MY_VK_DIRE_DOWN:
            if (this->curPos.Y != 9)
                this->moveCur({ 0,1 });
            break;
        case MY_VK_DIRE_LEFT:
            if (this->curPos.X != 0)
                this->moveCur({ -1,0 });
            break;
        case MY_VK_DIRE_RIGHT:
            if (this->curPos.X != 9)
                this->moveCur({ 1,0 });
            break;
        default:break;
        }
        break;
    default:break;
    }
}
void Game::moveCur(COORD opp)
{
    this->curPos.X += opp.X;
    this->curPos.Y += opp.Y;
    this->pen.SetCursorPos({ short(this->curPos.X << 1),short(this->curPos.Y + this->originalY) });
}

void Game::moveCurTo(COORD abs)
{
    this->curPos = abs;
    this->pen.SetCursorPos({ short(this->curPos.X << 1),short(this->curPos.Y + this->originalY) });
}
char cntChecked = '1';
void Game::onclick()
{
    if (this->curPos.X == 0)
    {
        if (this->curPos.Y == 0 || this->curPos.Y == 9)
            return;
        if (this->visitedLeft.at(this->curPos.Y - 1))
            return;
        else
        {
            this->visitedLeft.set(this->curPos.Y - 1);
            COORD To = this->go(this->curPos, { 1,0 });
            if (To == this->curPos)
            {
                this->pen.Write(0x4f,'R');
                this->moveCur({ 0,0 });
            }
            else if (isLegalPosition(To))
            {
                this->pen.Write(0x68,'H');
                this->moveCur({ 0,0 });
            }
            else {
                COORD pos = this->curPos;
                this->pen.Write(cntChecked);
                this->moveCurTo(To);
                this->pen.Write(cntChecked);
                this->moveCurTo(pos);
                cntChecked++;
                this->visit(To);
            }
            return;
        }
    }
    if (this->curPos.X == 9)
    {
        if (this->curPos.Y == 0 || this->curPos.Y == 9)
            return;
        if (this->visitedRight.at(this->curPos.Y - 1))
            return;
        else
        {
            this->visitedRight.set(this->curPos.Y - 1);
            COORD To = this->go(this->curPos, { -1,0 });
            if (To == this->curPos)
            {
                this->pen.Write(0x4F,'R');
                this->moveCur({ 0,0 });
            }
            else if (isLegalPosition(To))
            {
                this->pen.Write(0x68,'H');
                this->moveCur({ 0,0 });
            }
            else {
                COORD pos = this->curPos;
                this->pen.Write(cntChecked);
                this->moveCurTo(To);
                this->pen.Write(cntChecked);
                this->moveCurTo(pos);
                cntChecked++;
                this->visit(To);
            }
            return;
        }
    }
    if (this->curPos.Y == 0)
    {
        if (this->visitedTop.at(this->curPos.X - 1))
            return;
        else
        {
            this->visitedTop.set(this->curPos.X - 1);
            COORD To = this->go(this->curPos, { 0,1 });
            if (To == this->curPos)
            {
                this->pen.Write(0x4f,'R');
                this->moveCur({ 0,0 });
            }
            else if (isLegalPosition(To))
            {
                this->pen.Write(0x68,'H');
                this->moveCur({ 0,0 });
            }
            else {
                COORD pos = this->curPos;
                this->pen.Write(cntChecked);
                this->moveCurTo(To);
                this->pen.Write(cntChecked);
                this->moveCurTo(pos);
                cntChecked++;
                this->visit(To);
            }
            return;
        }
    }
    if (this->curPos.Y == 9)
    {
        if (this->visitedBottom.at(this->curPos.X - 1))
            return;
        else
        {
            this->visitedBottom.set(this->curPos.X - 1);
            COORD To = this->go(this->curPos, { 0,-1 });
            if (To == this->curPos)
            {
                this->pen.Write(0x4f,'R');
                this->moveCur({ 0,0 });
            }
            else if (isLegalPosition(To))
            {
                this->pen.Write(0x68,'H');
                this->moveCur({ 0,0 });
            }
            else {
                COORD pos = this->curPos;
                this->pen.Write(cntChecked);
                this->moveCurTo(To);
                this->pen.Write(cntChecked);
                this->moveCurTo(pos);
                cntChecked++;
                this->visit(To);
            }
            return;
        }
    }
    if (this->visitedMid.at(this->curPos.X - 1, this->curPos.Y - 1))
    {
        this->pen.Write(0x07, "_ ");
        this->moveCur({ 0,0 });
        this->visitedMid.unset(this->curPos.X - 1, this->curPos.Y - 1);
    }
    else
    {
        this->pen.Write(0x06, "o ");
        this->moveCur({ 0,0 });
        this->visitedMid.set(this->curPos.X - 1, this->curPos.Y - 1);
    }
}


void Game::oncheck()
{
    if (this->visitedMid.count() != 4)
    {
        char* str = new char[64];
        sprintf_s(str, 64, "There are 4 hidden balls; you have placed %d.", int(this->visitedMid.count()));
        this->pen.Write({ 0,10 }, str);
        return;
    }
    this->ended = 1;
    int correct = 0;
    for (int i = 0; i < 8; i++)
    {
        for (int j = 0; j < 8; j++)
        {
            this->moveCurTo({ SHORT(i + 1),SHORT(j + 1) });
            if (this->placed.at(i, j) && this->visitedMid.at(i, j))
                this->pen.Write(0x02,  'o'), correct++;
            else if (this->placed.at(i, j))
                this->pen.Write(0x04,  'o');
            else if (this->visitedMid.at(i, j))
                this->pen.Write(0x04,  'x');
        }
    }
    if (correct == 4)
    {
        char* str = new char[128];
        this->moveCurTo({ 0,10 });
        sprintf_s(str, 64, "Right!    You score is %d", int(this->visitedBottom.count() + this->visitedLeft.count() + this->visitedRight.count() + this->visitedTop.count()));
        this->pen.Write(str);
        this->pen.NewLine();
    }
    else {
        char* str = new char[128];
        this->moveCurTo({ 0,10 });
        sprintf_s(str, 64, "Oops!  You missed %d balls.  You score is %d", 4 - correct, int((4 - correct) * 5 + this->visitedBottom.count() + this->visitedLeft.count() + this->visitedRight.count() + this->visitedTop.count()));
        this->pen.Write(str);
        this->pen.NewLine();
    }
}

void Game::visit(COORD pos)
{
    if (pos.X == 0)
    {
        if (pos.Y == 0 || pos.Y == 9)
            return;
        this->visitedLeft.set(pos.Y - 1);
    }
    if (pos.X == 9)
    {
        if (pos.Y == 0 || pos.Y == 9)
            return;
        this->visitedRight.set(pos.Y - 1);
    }
    if (pos.Y == 0)
    {
        this->visitedTop.set(pos.X - 1);
    }
    if (pos.Y == 9)
    {
        this->visitedBottom.set(pos.X - 1);
    }
}
